﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using CommandLineParser.Arguments;
using uTILLIty.Rave.Contract.Job;
using uTILLIty.Rave.Core.Manager;

namespace uTILLIty.Rave.Commandline
{
	/// <summary>
	/// The command uses the <see cref="ConfigurationManager"/> to process the supplied
	/// <see cref="ConfigurationFile"/> and (optional) <see cref="CsvFile"/>
	/// </summary>
	public class CreateCommand : BaseCreateCommand
	{
		private int _curLine;
		private readonly JobManager _jobManager;

		/// <summary>
		/// The CSV file with the parameters (1st line containing the fieldnames)
		/// </summary>
		[FileArgument('d', "data", Optional = true, Description = "The CSV file with the parameters (1st line containing the fieldnames)",
			Example = "-d input.csv")]
		public FileInfo CsvFile;

		/// <summary>
		/// The encoding of the CSV file (default = Windows-1252)
		/// </summary>
		[ValueArgument(typeof(string), 'e', "encoding", Optional = true, DefaultValue = "Windows-1252", Description = "The encoding of the CSV file (default = Windows-1252)",
			Example = "-e utf-8")]
		public string CsvEncoding = "Windows-1252";

		/// <summary>
		/// The separator within the CSV file (;)
		/// </summary>
		[ValueArgument(typeof(string), 'p', "separator", Optional = true, DefaultValue = ";", Description = "The separator within the CSV file (;)",
			Example = "-p ,")]
		public string CsvSeparator = ";";

		/// <summary>
		/// The configuration XML (will be validated against the supported XSD)
		/// </summary>
		[FileArgument('t', "template", Optional = false, Description = "The configuration XML (will be validated against the supported XSD)",
			Example = "-t configuration.xml")]
		public FileInfo ConfigurationFile;

		/// <summary>
		/// The filename to save the merged configuration to. A sequence and the .xml extension is added automatically
		/// </summary>
		[ValueArgument(typeof(bool), 'm', "mergeoutput", Optional = true, Description = "Wether to save the merged configuration. A sequence and the .xml extension is added automatically")]
		public bool SaveMergedOutput;

		/// <summary>
		/// Wether to not really do anything, just log what would be done
		/// </summary>
		[ValueArgument(typeof(bool), 's', "simulate", Optional = true, Description = "Wether to not really do anything, just log what would be done")]
		public bool Simulate;

		private readonly ConfigurationManager _configurationManager;

		/// <summary>
		/// Creates a new instance of the <see cref="CreateCommand"/>
		/// </summary>
		public CreateCommand(ConfigurationManager configurationManager, CsvManager csvManager, JobManager jobManager) : base(csvManager)
		{
			_configurationManager = configurationManager;
			_jobManager = jobManager;
		}

		/// <summary>
		/// Executes the command
		/// </summary>
		protected override void OnExecute()
		{
			var configFile = ConfigurationFile;

			var job = new Job
			{
				Template = configFile.FullName,
				ValidateAfterMerge = true,
				JobSource = configFile.FullName,
				SaveMergedOutput = SaveMergedOutput,
				LogLevel = LogLevel,
			};

			_curLine = 1;
			if (CsvFile == null)
			{
				Console.WriteLine(Properties.Resources.ProcessingConfiguration);
				Process(null, job);
			}
			else
			{
				job.JobSource = CsvFile.FullName;
				ProcessMultiple(job);
			}
		}

		private void InizializeJobLog(Job job)
		{
			if (job.TraceSource != null)
			{
				job.TraceSource.Flush();
				job.TraceSource.Close();
				job.TraceSource = null;
			}
			_jobManager.InitializeLogging(job);
			// ReSharper disable PossibleNullReferenceException
			job.TraceSource.Listeners.Add(new ConsoleTraceListener());
			// ReSharper restore PossibleNullReferenceException
		}

		private void ProcessMultiple(Job job)
		{
			CsvManager.CsvSeparator = CsvSeparator;
			using (var csv = new StreamReader(CsvFile.OpenRead(), Encoding.GetEncoding(CsvEncoding)))
			{
				var head = CsvManager.ReadNextRecord(csv);
				if (head == null)
				{
					Console.WriteLine(Properties.Resources.NoLinesToProcess);
					return;
				}

				while (!csv.EndOfStream)
				{
					var values = CsvManager.ReadNextRecord(csv);
					if (values == null)
						continue;

					_curLine++;
					Console.WriteLine(Properties.Resources.ProcessingLine, _curLine);

					var mergeVariables = new List<MergeVariable>(head.Length);
					for (int i = 0; i < head.Length; i++)
					{
						var key = head[i];
						var value = values.Length > i ? values[i] : "";
						var mergeVar = MergeVariable.Create(key, value);
						mergeVariables.Add(mergeVar);
					}

					try
					{
						UpdateJobId(job, _curLine);
						Process(mergeVariables, job);
					}
					catch (Exception ex)
					{
						WriteException(ex);
					}
				}
			}
			Console.WriteLine(Properties.Resources.ProcessingCompleted, _curLine);
		}

		private void Process(List<MergeVariable> mergeVariables, Job job)
		{
			Console.WriteLine(Properties.Resources.JobStarted, job.JobId);
			InizializeJobLog(job);
			if (mergeVariables == null)
				mergeVariables = new List<MergeVariable>(20);
			if (!string.IsNullOrWhiteSpace(MergeVariables))
				ParseCommandlineMergeVariable(mergeVariables, MergeVariables, job.GetEncoding());

			job.MergeVariables = mergeVariables.ToArray();

			var configuration = _configurationManager.LoadAndParse(job);
			_configurationManager.Apply(job, configuration, Simulate);
			Console.WriteLine(Properties.Resources.JobCompleted, job.JobId);
		}

		protected static internal void UpdateJobId(Job job, int curLine)
		{
			var idSuffix = curLine.ToString("-0000");
			var lastIdx = job.JobId.LastIndexOf('-');
			job.JobId = ((lastIdx < 0) ? job.JobId : job.JobId.Substring(0, lastIdx)) + idSuffix;
		}
	}
}